﻿namespace Mp3Observer.Common.IoC
{
    #region usings

    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using Mp3Observer.Common.Services; 

    #endregion

    /// <summary>
    /// A Deferred DependencyResolver (Instance creation on Resolve)
    /// </summary>
    public sealed class DependencyResolver : IDependencyResolver
    {
        /// <summary>
        /// dictionary of the unique dependecies (key: type + name)
        /// </summary>
        private readonly Dictionary<DependencyItemKey, DependencyItemValue> types = new Dictionary<DependencyItemKey, DependencyItemValue>();

        /// <summary>
        /// Initializes a new instance of the <see cref="DependencyResolver"/> class.
        /// </summary>
        public DependencyResolver()
        {
            var configuration = IoCSettingsConfigSection.Settings;
            foreach (ComponentElement component in configuration.Components)
            {
                try
                {
                    this.Register(component.Name, Type.GetType(component.Contract, true, true),
                        Type.GetType(component.Implementation, true, true),
                        component.Constructors);
                }
                catch (Exception ex)
                {
                    string message = "A configured component is not valid. Contract='{0}', Implementation={1}";
                    message = String.Format(message, component.Contract, component.Implementation);
                    throw new ConfigurationErrorsException(message, ex);
                }
            }
        }

        /// <summary>
        /// Registers the type, BUT: Implementation is deferred created when Resolve is called.
        /// </summary>
        /// <param name="name">The name of the Dependency.</param>
        /// <param name="contractType">Type of the Dependency.</param>
        /// <param name="implementationType">Type of the implementation.</param>
        /// <param name="constructors">ctors must be already registered.</param>
        public void Register(string name, Type contractType, Type implementationType, ConstructorCollection constructors)
        {
            if (contractType.IsAssignableFrom(implementationType) == false)
                throw new InvalidOperationException(String.Format("The supplied instance does not implement {0}", contractType.FullName));

            // if (types.ContainsKey(contractType))
            //    types.Remove(contractType);
            this.types.Add(new DependencyItemKey() { Name = name, ContractType = contractType },
                new DependencyItemValue() { Name = name, ContractType = contractType, ImplementationType = implementationType, Constructors = constructors });
        }

        #region IDependencyResolver Members

        /// <summary>
        /// Resolves the instance by type and name.
        /// </summary>
        /// <typeparam name="T">&gt;type in the dictionary.</typeparam>
        /// <param name="name">The name (player1,player2).</param>
        /// <returns>the implementation</returns>
        public T ResolveByName<T>(string name)
        {
            var key = new DependencyItemKey() { ContractType = typeof(T), Name = name };
            var item = this.types[key];
            this.GenerateImplementation(item);
            var impl = (T)this.types[key].Implementation;
            // this.types.Remove(key);
            return impl;
        }

        /// <summary>
        /// Resolves the instance by type.
        /// </summary>
        /// <typeparam name="T">type in the dictionary.</typeparam>
        /// <returns>the implementation</returns>
        public T Resolve<T>()
        {
            var key = new DependencyItemKey() { ContractType = typeof(T), Name = typeof(T).Name };
            var item = this.types[key];
            this.GenerateImplementation(item);
            return (T)this.types[key].Implementation;
        }

        #endregion

        /// <summary>
        /// Generates the implementation.
        /// </summary>
        /// <param name="item">The DependencyItemValue.</param>
        private void GenerateImplementation(DependencyItemValue item)
        {
            if (item.IsGenerated)
                return;
            if (item.Constructors.Count == 0)
                item.Implementation = Activator.CreateInstance(item.ImplementationType);
            else
            {
                var args = new List<object>();
                foreach (ConstructorElement element in item.Constructors)
                {
                    Type t = Type.GetType(element.Dependency, true, true);
                    var key = new DependencyItemKey() { ContractType = t, Name = t.Name };
                    var typeitem = this.types[key];
                    this.GenerateImplementation(typeitem);
                    args.Add(typeitem.Implementation);
                }
                item.Implementation = Activator.CreateInstance(item.ImplementationType, args.ToArray());
            }
            // Register the instance
            if (item.ImplementationType.GetInterface("IObserver") != null)
                Observer.Instance.Register((IObserver)item.Implementation);
            item.IsGenerated = true;
        }
    }
}
